#include <nan.h>
#include "base.h"
#include "context.h"
#include "channel.h"
#include "video_device.h"
#include "meeting_video_frame_interface.h"
#include "meeting_video_frame.h"

using v8::Persistent;
using v8::Function;
using v8::String;
using v8::Value;
using v8::Local;
using v8::Number;
using v8::HandleScope;
using v8::Isolate;
using v8::Object;
using v8::FunctionTemplate;

// =============== NAN ERROR DEFINE. =================
const int kNanErrorBegin                = 100;
const int kNanEerrorArgsLengthInvalied  = kNanErrorBegin + 1;
const int kNanErrorArgsTypeInvalied     = kNanEerrorArgsLengthInvalied + 1;
// =============== NAN ERROR DEFINE. =================

const int kOnPpreviewCallbackParamsNum = 8;

class RenderModel;

std::unique_ptr<IMeetingVideoFrameController> video_frame_controller = nullptr;
std::unique_ptr<RenderModel> render_model = nullptr;
Persistent<Function> on_preview_callback;

// Render Enum Define.
namespace Render {
  enum RenderCallFunc {
  };

  enum RenderEvent {
    kEventVideoFrame                = 1,
  };

  enum FrameType {
    kVideoFrame                     = 1,
    kVideolayer                     = 2,
  };
}

void MyOnVideoFrame(const tagMeetingVideoFrame& frame) {
  auto isolate = Isolate::GetCurrent();
  HandleScope scope(isolate);

  // The param order is the same as wmp av api.
  auto size = Nan::New(frame.size);
  auto format = Nan::New(frame.format);
  auto width = Nan::New(frame.width);
  auto height = Nan::New(frame.height);
  auto rotate = Nan::New(frame.rotate);
  auto uin = Nan::New<Number>(frame.uin);
  auto data = Nan::CopyBuffer((char *)frame.data, frame.size).ToLocalChecked();
  auto stream = String::NewFromUtf8(isolate, frame.stream);

  v8::Local<v8::Value> argv[kOnPpreviewCallbackParamsNum] = {size, data, format, width, height, rotate, uin, stream};

  auto callback = Local<Function>::New(isolate, on_preview_callback);
  callback->Call(isolate->GetCurrentContext()->Global(), kOnPpreviewCallbackParamsNum, argv);
}

class RenderModel : public IMeetingVideoFrameCtrlDelegate {
public:
  RenderModel() {
    video_frame_controller->SetDelegate(this);
  }

  virtual ~RenderModel() {
    video_frame_controller->SetDelegate(nullptr);
  }

  void OnVideoFrame(const tagMeetingVideoFrame& frame) {
    MyOnVideoFrame(frame);
  }
};

void Add(const Nan::FunctionCallbackInfo<Value>& args) {
  int sum = kNanErrorBegin;
  do {
    if (args.Length() != 2) {
      Nan::ThrowTypeError("Wrong number of arguments");
      sum = kNanEerrorArgsLengthInvalied;
      break;
    }

    if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
      Nan::ThrowTypeError("Wrong arguments");
      sum = kNanErrorArgsTypeInvalied;
      break;
    }

    double num1 = args[0]->NumberValue();
    double num2 = args[1]->NumberValue();
    sum = num1 + num2;
  } while (false);
  
  args.GetReturnValue().Set(Nan::New(sum));
}

void StartUp(const Nan::FunctionCallbackInfo<Value>& args) {
  int ret = kNanErrorBegin;
  do {
    if (args.Length() != 3) {
      Nan::ThrowTypeError("Wrong number of arguments");
      ret = kNanEerrorArgsLengthInvalied;
      break;
    }

    if (!args[0]->IsNumber() || !args[1]->IsInt32() || !args[2]->IsBoolean()) {
      Nan::ThrowTypeError("Wrong arguments");
      ret = kNanErrorArgsTypeInvalied;
      break;
    }
    uint64_t uin = args[0]->IntegerValue();
    int32_t appid = args[1]->Int32Value();
    bool test_env = args[1]->BooleanValue();

    ret = Context::StartUp(uin, appid, test_env);
  } while (false);
  args.GetReturnValue().Set(Nan::New(ret));
}

void Join(const Nan::FunctionCallbackInfo<Value>& args) {
  int ret = kNanErrorBegin;
  do {
    if (args.Length() != 5) {
      Nan::ThrowTypeError("Wrong number of arguments");
      ret = kNanEerrorArgsLengthInvalied;
    }

    if (!args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsNumber() || !args[3]->IsUint8Array() || !args[4]->IsUint32()) {
      Nan::ThrowTypeError("Wrong arguments");
      ret = kNanErrorArgsTypeInvalied;
    }

    uint64_t app_id = args[0]->IntegerValue();
    uint64_t identifier = args[1]->IntegerValue();
    uint64_t channel_id = args[2]->IntegerValue();
    auto v8_str = Local<String>::Cast(args[3]);
    String::Utf8Value auth(v8_str);
    uint32_t len = args[4]->Uint32Value();

    ret = Channel::Join(app_id, identifier, channel_id, *auth, len);
  } while (false);

  args.GetReturnValue().Set(Nan::New(ret));
}

void Preview(const Nan::FunctionCallbackInfo<Value>& args) {
  int ret = kNanErrorBegin;
  do {
    auto isolate = Isolate::GetCurrent();
    HandleScope scope(isolate);

    if (args.Length() != 3) {
      Nan::ThrowTypeError("Wrong number of arguments");
      ret = kNanEerrorArgsLengthInvalied;
    }

    if (!args[0]->IsString() || !args[1]->IsBoolean()) {
      Nan::ThrowTypeError("Wrong arguments");
      ret = kNanErrorArgsTypeInvalied;
    }

    auto v8_str = Local<String>::Cast(args[0]);
    String::Utf8Value device_id(v8_str);

    bool on = args[1]->BooleanValue();
    ret = Device::Video::Preview(*device_id, on);

    Local<Function> local_callback = Local<Function>::Cast(args[2]);
    on_preview_callback.Reset(isolate, local_callback);
  } while (false);

  args.GetReturnValue().Set(Nan::New(ret));
}

void Init(Local<Object> exports) {
  // for test, js & c++ interactive.
  exports->Set(Nan::New("add").ToLocalChecked(), Nan::New<FunctionTemplate>(Add)->GetFunction());

  // ================== render video interface. =================
  exports->Set(Nan::New("startUp").ToLocalChecked(), Nan::New<FunctionTemplate>(StartUp)->GetFunction());

  exports->Set(Nan::New("join").ToLocalChecked(), Nan::New<FunctionTemplate>(Join)->GetFunction());

  exports->Set(Nan::New("preview").ToLocalChecked(), Nan::New<FunctionTemplate>(Preview)->GetFunction());
  // ================== render video interface. =================

  video_frame_controller = make_unique<MeetingVideoFrameController>();
  render_model = make_unique<RenderModel>();
}

NODE_MODULE(main, Init)